﻿using Abp.Dependency;
using GN.Framework.Extensions;
using GN.Framework.Utils;
using Nito.AsyncEx;
using StackExchange.Redis;
using System;
using System.Collections.Generic;
using System.Configuration;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace GN.Pay.Core.Runtime.Caching.Impl
{
    /// <summary>
    /// 
    /// </summary>
    class RedisBaseCache : ARedisBase, IBaseCache, ITransientDependency
    {
        protected readonly object SyncObj = new object();
        private readonly AsyncLock _asyncLock = new AsyncLock();
        private readonly IRedisProvider databaseProvider;
        
        /// <summary>
        /// 
        /// </summary>
        /// <param name="name"></param>
        public RedisBaseCache(string name,
            IRedisProvider databaseProvider)
        {
            Name = name;
            this.databaseProvider = databaseProvider;
        }

        public TimeSpan? DefaultExpireTime { get; set; }

        private string GetLocalizedKey(string key)
        {
            return ("n:" + Name + ",v:" + key).ToUpperInvariant();
        }

        public string Name { get; private set; }

        public void Clear()
        {
            var database = databaseProvider.GetDatabase();
            database.KeyDeleteWithPrefix(GetLocalizedKey("*"));
        }

        public async Task ClearAsync()
        {
            await Task.Run(() =>
            {
                Clear();
            });
        }

        /// <summary>
        /// 
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="key"></param>
        /// <param name="factory"></param>
        /// <returns></returns>
        public T GetOrAdd<T>(string key, Func<string, T> factory)
        {
            var cacheKey = key;
            var item = Get<T>(key);
            if (item == null)
            {
                lock (SyncObj)
                {
                    item = Get<T>(key);
                    if (item == null)
                    {
                        item = factory(key);
                        if (item == null)
                        {
                            return default(T);
                        }
                        Set(cacheKey, item);
                    }
                }
            }
            return item;
        }

        /// <summary>
        /// 
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="key"></param>
        /// <param name="factory"></param>
        /// <returns></returns>
        public async Task<T> GetOrAddAsync<T>(string key, Func<string, Task<T>> factory)
        {
            var cacheKey = key;
            var item = await GetAsync<T>(key);
            if (item == null)
            {
                using (await _asyncLock.LockAsync())
                {
                    item = await GetAsync<T>(key);
                    if (item == null)
                    {
                        item = await factory(key);
                        if (item == null)
                        {
                            return default(T);
                        }
                        await SetAsync(cacheKey, item);
                    }
                }
            }
            return item;
        }


        /// <summary>
        /// 
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="key"></param>
        /// <returns></returns>
        public T Get<T>(string key)
        {
            var database = databaseProvider.GetDatabase();
            var objbyte = database.StringGet(GetLocalizedKey(key));
            return Deserialize<T>(objbyte);
        }

        public async Task<T> GetAsync<T>(string key)
        {
            return await Task.Run(() =>
            {
                return Get<T>(key);
            });
        }

        public void Remove(string key)
        {
            var database = databaseProvider.GetDatabase();
            database.KeyDelete(GetLocalizedKey(key));
        }

        public async Task RemoveAsync(string key)
        {
            await Task.Run(() =>
            {
                Remove(key);
            });
        }


        public void Set<T>(string key, T value, TimeSpan? expireTime = default(TimeSpan?))
        {
            if (value == null)
            {
                throw new ArgumentNullException(nameof(value));
            }
            var type = value.GetType();
            var database = databaseProvider.GetDatabase();
            database.StringSet(
                GetLocalizedKey(key),
                Serialize(value),
                expireTime ?? DefaultExpireTime
                );
        }

        /// <summary>
        /// 
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="key"></param>
        /// <param name="value"></param>
        /// <param name="slidingExpireTime"></param>
        /// <param name="absoluteExpireTime"></param>
        /// <returns></returns>
        public async Task SetAsync<T>(string key, T value, TimeSpan? expireTime = default(TimeSpan?))
        {
            await Task.Run(() =>
            {
                Set<T>(key, value, expireTime);
            });
        }

        public void Dispose()
        {
            lock (SyncObj)
            {
                databaseProvider.Dispose();
            }
        }
    }
}
